// Datapumps for importing via libgphoto2.
// Copyright 2006 (C) Ralph Thomas

#include <datapumps/progress.h>
#include <iostream>
#include <binder.hpp>
#include <adobe/dictionary.hpp>
#include <boost/shared_ptr.hpp>
#include <boost/weak_ptr.hpp>
#include <toolkit/application.h>
#include <apps/photo/photodb.h>
#include <apps/photo/import.h>
#include <apps/photo/camera_monitor.h>
#include <util/thread.h>
#include <util/registry.h>
#include <util/db.h>
#include <map> // for std::pair without the nagging
#include <stdarg.h>
#include <boost/filesystem/path.hpp>
#include <boost/filesystem/convenience.hpp>
#include <boost/filesystem/fstream.hpp>
#include <sstream>
extern "C" {
#include <gphoto2/gphoto2-context.h>
#include <gphoto2/gphoto2-camera.h>
#include <gphoto2/gphoto2-abilities-list.h>
}

namespace tk = toolkit;
namespace bfs = boost::filesystem;

namespace apps {
	namespace photo {
		struct gphoto_import_progress_t {
			datapumps::progress_report_t	progress_m;
			unsigned int					count_m;
			unsigned int					current_m;
			float							gp_target_m;
			float							gp_current_m;
			std::string						url_m;

			gphoto_import_progress_t() : progress_m( "Photo Import" ) {}
			void set_target(float target) {
				gp_target_m = target;
				if (target == 0.0f) return;
				update();
			}
			void set_current(float current) {
				gp_current_m = current;
				if (gp_target_m == 0.0f) return;
				update();
			}
			void set_photo_count(unsigned int count) {
				count_m = count;
				update();
			}
			void set_current_photo(const std::string& url, unsigned int idx) {
				current_m = idx;
				url_m = url;
				update();
			}
			void update() {
				int target = count_m * 1000;
				int current = current_m * 1000;
				if (gp_target_m > 0.0) current += (unsigned int)((gp_current_m / gp_target_m) * 1000.0);
				progress_m.set_count(target);
				progress_m.state_progress(current, url_m);
			}
		};
		struct gphoto_import_t {
			enum state_t {
				kLoading,
				kReady,
				kImporting
			};
			typedef std::pair<std::string, std::string>		camera_t;
			typedef boost::function<void (state_t)>			state_function_t;
			typedef boost::function<void (const adobe::array_t&)>	camera_list_function_t;
			typedef boost::function<void (const camera_t&)>		selected_camera_function_t;
			typedef boost::signal<void (state_t)>			state_signal_t;
			typedef boost::signal<void (const adobe::array_t&)>	camera_list_signal_t;
			typedef boost::signal<void (const camera_t&)>		selected_camera_signal_t;
			typedef boost::signals::scoped_connection		scoped_connection_t;
			typedef boost::signals::connection			connection_t;
			typedef boost::shared_ptr<gphoto_import_t>		gphoto_import_ptr_t;
			typedef boost::weak_ptr<gphoto_import_t>		weak_gphoto_import_ptr_t;
			~gphoto_import_t() {
				gp_context_unref( context_m );
				if( abilities_list_m ) gp_abilities_list_free( abilities_list_m );
				if( port_info_list_m ) gp_port_info_list_free( port_info_list_m );
			}
			//
			/// Return the single instance to the gphoto_import_t singleton.
			//
			static gphoto_import_ptr_t getInstance() {
				//
				// We use a weak pointer to the instance here so that when
				// nobody is using any gphoto datapumps, the structures here
				// get destroyed (and threads released, etc).
				//
				static weak_gphoto_import_ptr_t instance;
				gphoto_import_ptr_t actual( instance.lock() );
				if( actual ) return actual;
				actual = gphoto_import_ptr_t( new gphoto_import_t() );
				instance = actual;
				return actual;
			}
			//
			/// Subscribe to changes in the state of libgphoto2.
			///
			/// \param	fn	the function to call when the state changes.
			//
			connection_t subscribe_state( const state_function_t& fn ) {
				connection_t c( state_signal_m.connect( fn ) );
				state_signal_m( state_m );
				return c;
			}
			//
			/// Return the current state of libgphoto2.
			//
			state_t get_state() const { return state_m; }
			//
			/// Subscribe to the list of cameras.
			///
			/// \param	fn	the function to call when the camera list changes.
			//
			connection_t subscribe_camera_list( const camera_list_function_t& fn ) {
				return camera_list_signal_m.connect( fn );
			}
			//
			/// Subscribe to the currently selected camera.
			///
			/// \param	fn	the function to call when the selected camera changes.
			//
			connection_t subscribe_selected_camera( const selected_camera_function_t& fn ) {
				return selected_camera_signal_m.connect( fn );
			}
			//
			/// Rescan the system for cameras.
			//
			void rescan_cameras() {
				thread_m.adoptWork( boost::bind( &gphoto_import_t::scan_cameras, this, context_m, abilities_list_m, port_info_list_m ) );
			}
			//
			/// Return the current list of cameras.
			///
			/// \return	the current list of cameras.
			//
			adobe::array_t get_camera_list() const { return cameras_m; }
			//
			/// Set the camera to import from.
			///
			/// \param	camera	the camera to import from.
			//
			void set_camera( const camera_t& camera ) {
				camera_m = camera;
				selected_camera_signal_m( camera_m );
			}
			//
			/// Get the currently selected camera to import from.
			///
			/// \return	the currently selected camera to import from.
			//
			camera_t get_camera() const { return camera_m; }
			//
			/// Import all photos into the given album.
			///
			/// \param	album	the album to import photos into.
			//
			void import( const std::string& album, const bfs::path& target ) {
				if( !bfs::exists( target ) ) return;
				//
				// Schedule some thread work.
				//
				thread_m.adoptWork( boost::bind( &gphoto_import_t::do_import, this, context_m, abilities_list_m, port_info_list_m, camera_m, album, target ) );
			}
		  private:
			state_t				state_m;
			util::thread			thread_m;
			state_signal_t			state_signal_m;
			camera_list_signal_t		camera_list_signal_m;
			selected_camera_signal_t	selected_camera_signal_m;
			adobe::array_t			cameras_m;
			camera_t			camera_m;
			GPContext*			context_m;
			CameraAbilitiesList*		abilities_list_m;
			GPPortInfoList*			port_info_list_m;
			gphoto_import_progress_t*	progress_m;
			camera_monitor_t::camera_monitor_ptr_t	camera_monitor_m;
			camera_monitor_t::scoped_connection_t	camera_add_m;
			camera_monitor_t::scoped_connection_t	camera_remove_m;

			gphoto_import_t() : state_m( kLoading ), context_m( gp_context_new() ), abilities_list_m( 0 ), port_info_list_m( 0 ), progress_m( 0 ) {
				gp_context_set_error_func( context_m, &gphoto_import_t::gp_message, this );
				gp_context_set_status_func( context_m, &gphoto_import_t::gp_message, this );
				gp_context_set_message_func( context_m, &gphoto_import_t::gp_message, this );
				gp_context_set_progress_funcs( context_m,
					&gphoto_import_t::gp_progress_start,
					&gphoto_import_t::gp_progress_update,
					&gphoto_import_t::gp_progress_stop,
					this );
				thread_m.adoptWork( boost::bind( &gphoto_import_t::scan_cameras, this, context_m, abilities_list_m, port_info_list_m ) );
				camera_monitor_m = camera_monitor_t::get_instance();
				if( camera_monitor_m ) {
					camera_add_m = camera_monitor_m->on_camera_add( boost::bind( &gphoto_import_t::rescan_cameras, this ) );
					camera_remove_m = camera_monitor_m->on_camera_remove( boost::bind( &gphoto_import_t::rescan_cameras, this ) );
				}
			}
			static void gp_message( GPContext* context, const char* format, va_list args, void* v_us ) {}
			static unsigned int gp_progress_start( GPContext*, float target, const char*, va_list, void* v_us ) {
				gphoto_import_t* us( reinterpret_cast<gphoto_import_t*>( v_us ) );
				if( !us || !us->progress_m ) return 0;
				us->progress_m->set_target( target );
				return 0;
			}
			static void gp_progress_update( GPContext*, unsigned int, float current, void* v_us ) {
				gphoto_import_t* us( reinterpret_cast<gphoto_import_t*>( v_us ) );
				if( !us || !us->progress_m ) return;
				us->progress_m->set_current( current );
			}
			static void gp_progress_stop( GPContext*, unsigned int, void* v_us ) {
				gphoto_import_t* us( reinterpret_cast<gphoto_import_t*>( v_us ) );
				if( !us || !us->progress_m ) return;
				us->progress_m->set_target( 0.0f );
				us->progress_m->set_current( 0.0f );
			}
			static void scan_cameras( gphoto_import_t* us, GPContext* context, CameraAbilitiesList* abilities_list, GPPortInfoList* port_list ) {
				if( !abilities_list || !port_list ) {
					if( abilities_list ) gp_abilities_list_free( abilities_list );
					if( port_list ) gp_port_info_list_free( port_list );
					//
					// Initialize the gphoto library.
					//
					gp_abilities_list_new( &abilities_list );
					gp_port_info_list_new( &port_list );
					gp_abilities_list_load( abilities_list, context );
					gp_port_info_list_load( port_list );
				}
				//
				// Get the list of cameras.
				//
				CameraList* list;
				gp_list_new( &list );
				gp_abilities_list_detect( abilities_list, port_list, list, context );
				//
				// Build an adobe::array of adobe::dictionaries that contain
				// the name and model of the camera.
				//
				int count = gp_list_count( list );
				adobe::array_t cameras;
				//
				// On libgphoto2 2.1.6 there seem to be duplicate cameras
				// listed, so we keep track of the last camera added and
				// don't add it twice... Not too sure how to fix this, as
				// sometimes the duplicates have different port information.
				//
				// This is fixed on libgphoto2 2.2.0.
				//
				for( int i = 0; i < count; i++ ) {
					adobe::dictionary_t dict;
					const char* model( 0 );
					const char* port( 0 );
					camera_t camera;

					gp_list_get_name( list, i, &model );
					gp_list_get_value( list, i, &port );
					camera.first = std::string( model );
					camera.second = std::string( port );

					dict = dict.set( adobe::static_name_t( "name" ), adobe::value_t( std::string( model ) ) );
					dict = dict.set( adobe::static_name_t( "value" ), adobe::value_t( camera ) );

					cameras.push_back( adobe::value_t( dict ) );
				}
				gp_list_free( list );
				//
				// Inform the gphoto_import_t in the main thread
				//
				tk::application::getInstance().executeInMain( boost::bind( &gphoto_import_t::on_camera_list, us, cameras, abilities_list, port_list ) );
			}
			void on_camera_list( adobe::array_t cameras, CameraAbilitiesList* list, GPPortInfoList* ports ) {
				if( abilities_list_m && (abilities_list_m != list) ) gp_abilities_list_free( abilities_list_m );
				if( port_info_list_m && (port_info_list_m != ports) ) gp_port_info_list_free( port_info_list_m );
				cameras_m = cameras;
				abilities_list_m = list;
				port_info_list_m = ports;
				state_m = kReady;
				//
				// Send the list first, so that any UI that wants
				// to highlight the selected camera knows what to
				// highlight (before we send the selected camera).
				//
				camera_list_signal_m( cameras_m );
				//
				// If we don't have any camera selected currently,
				// or if the selection has gone away then select
				// the first camera in the list.
				//
				if( (camera_m.first == "") && !cameras_m.empty() ) {
					adobe::dictionary_t dict;
					if( cameras_m.get( 0, dict ) ) {
						dict.get( adobe::static_name_t( "value" ), camera_m );
						selected_camera_signal_m( camera_m );
					}
				}
				state_signal_m( state_m );
			}
			typedef std::vector<bfs::path>		file_list_t;

			static void get_folder_list( GPContext* context, Camera* cam, const bfs::path& folder, file_list_t& out ) {
				CameraList* list( 0 );
				
				gp_list_new( &list );
				gp_camera_folder_list_folders( cam, folder.native_file_string().c_str(), list, context );
				
				int count( gp_list_count( list ) );
				for( int i = 0; i < count; i++ ) {
					const char* name( 0 );
					gp_list_get_name( list, i, &name );
					out.push_back( folder / bfs::path( name, bfs::native ) );
				}
				gp_list_free( list );
			}
			static void get_file_list( GPContext* context, Camera* cam, const bfs::path& folder, file_list_t& out ) {
				CameraList* list( 0 );

				gp_list_new( &list );
				gp_camera_folder_list_files( cam, folder.native_file_string().c_str(), list, context );
				int count( gp_list_count( list ) );

				for( int i = 0; i < count; i++ ) {
					const char*	name( 0 );
					CameraFileInfo	info;

					gp_list_get_name( list, i, &name );
					if( gp_camera_file_get_info( cam, folder.native_file_string().c_str(), name, &info, NULL ) != GP_OK ) continue;
					//
					// Validate the MIME type or file extension.
					//
					bfs::path image( name, bfs::native );
					std::string extension( bfs::extension( image ) );
					if( (std::string( info.file.type ) == "image/jpeg") || (extension == ".jpg") || (extension == ".jpeg") )
						out.push_back( folder / bfs::path( info.file.name, bfs::native ) );
				}
				gp_list_free( list );
			}
			static void get_all_files( GPContext* context, Camera* cam, const bfs::path& folder, file_list_t& out ) {
				file_list_t folder_list;
				//
				// Put all of the filenames into our out list.
				//
				get_file_list( context, cam, folder, out );
				get_folder_list( context, cam, folder, folder_list );
				for( file_list_t::iterator i = folder_list.begin(); i != folder_list.end(); i++ ) {
					get_all_files( context, cam, *i, out );
				}
			}
			static void single_result( std::string& val, const adobe::name_t& name, const adobe::dictionary_t& dict ) {
				val = dict.get<std::string>( name );
			}
			static void do_import( gphoto_import_t* us, GPContext* context, CameraAbilitiesList* list, GPPortInfoList* port, camera_t camera, std::string album, bfs::path target ) {
				//
				// Inform everybody that we are starting to import.
				//
				tk::application::getInstance().executeInMain( boost::bind( &gphoto_import_t::set_state, us, kImporting ) );
				us->progress_m = new gphoto_import_progress_t;
				//
				// Begin the import.
				//
				int r( gp_abilities_list_lookup_model( list, camera.first.c_str() ) );
				if( r < 0 ) { std::cerr << "can't lookup model: " << camera.first << std::endl; return; }
				CameraAbilities a;
				r = gp_abilities_list_get_abilities( list, r, &a );
				if( r < 0 ) { std::cerr << "can't get abilities for model: " << camera.first << std::endl; return; }
				Camera* cam( 0 );
				gp_camera_new( &cam );
				gp_camera_set_abilities( cam, a );
				r = gp_port_info_list_lookup_path( port, camera.second.c_str() );
				if( r < 0 ) { std::cerr << "can't lookup port: " << camera.second << std::endl; return; }
				GPPortInfo port_info;
				gp_port_info_list_get_info( port, r, &port_info ); /* XXX: is that right, using r there? */
				gp_camera_set_port_info( cam, port_info );
				//
				// Find all of the images on the camera.
				//
				file_list_t images;
				get_all_files( context, cam, bfs::path( "/", bfs::native ), images );
				us->progress_m->set_photo_count(images.size());
				//
				// Now download them.
				//
				try {
					std::string serial_number;
					util::db_ptr_t db( util::db::fromPath( getPhotoDatabasePath() ) );
					int idx = 0;
					for( file_list_t::const_iterator i = images.begin(); i != images.end(); i++ ) {
						//
						// We really don't need to do this every time, as we should be the
						// only writer.
						//
						db->query( "select count(*) as num from photos;", boost::bind( &gphoto_import_t::single_result, boost::ref( serial_number ), adobe::static_name_t( "num" ), _1 ) );
						//
						// Create the target filename.
						//
						bfs::path target_file( target );
						target_file /= bfs::path( serial_number + ".jpg", bfs::native );
						target_file.normalize();
						//
						// If this file already exists then erase it.
						//
						if( bfs::exists( target_file ) ) bfs::remove( target_file );
						//
						// Import the file. This method (reading everything into memory and
						// then writing it out again) is the "proper" way to do things with
						// libgphoto2 apparently. I hope that the data is mmap'd and so not
						// growing the heap to 10s of megabytes...
						//
						CameraFile* src( 0 );
						gp_file_new( &src );
						gp_camera_file_get( cam, i->branch_path().native_directory_string().c_str(),
							i->leaf().c_str(), GP_FILE_TYPE_NORMAL, src, context );
						gp_file_save( src, target_file.native_file_string().c_str() );
						gp_file_unref( src );
						//
						// Use the import function to have the image interrogated and
						// added to the database.
						//
						import_image( target_file, target_file, kReference, album );
						//
						// Now inform everybody of our progress.
						//
						std::string url( "file://" );
						url += target_file.native_file_string();
						idx++;
						us->progress_m->set_current_photo( url, idx );
					}
				} catch( const std::exception& ex ) {
					std::cout << "GPHOTO IMPORT: ERROR: " << ex.what() << std::endl;
				}
				gp_camera_free( cam );
				delete us->progress_m; us->progress_m = 0;
				tk::application::getInstance().executeInMain( boost::bind( &gphoto_import_t::set_state, us, kReady ) );
			}
			void set_state( state_t state ) {
				state_m = state;
				state_signal_m( state_m );
			}
		};
		//
		// Datapumps that use gphoto_import_t.
		//
		class gphoto_state_pump_t : public adobe::binder_t {
			adobe::sheet_t::monitor_value_t		value_monitor_m;
			gphoto_import_t::scoped_connection_t	connection_m;
			gphoto_import_t::gphoto_import_ptr_t	ref_m;
			void update( gphoto_import_t::state_t s ) {
				if( !value_monitor_m ) return;
				std::string sval;
				switch( s ) {
				  case gphoto_import_t::kLoading:
					  sval = "loading";
					  break;
				  case gphoto_import_t::kReady:
					  sval = "ready";
					  break;
				  case gphoto_import_t::kImporting:
					  sval = "importing";
					  break;
				}
				value_monitor_m( adobe::value_t( sval ) );
			}
		  public:
			gphoto_state_pump_t() {}
			virtual ~gphoto_state_pump_t() {}
			void set( const adobe::value_t& ) {}
			void set_signals( const adobe::sheet_t::monitor_value_t& value_monitor, const adobe::sheet_t::monitor_active_t& active_monitor ) {
				value_monitor_m = value_monitor;
				if( active_monitor ) active_monitor( false );
				if( !ref_m ) {
					ref_m = gphoto_import_t::getInstance();
					connection_m = ref_m->subscribe_state( boost::bind( &gphoto_state_pump_t::update, this, _1 ) );
				} else update( ref_m->get_state() );
			}
			void substitute( const adobe::name_t&, const adobe::value_t& ) {}
			void invoke() {}
			adobe::binder_ptr_t copy() const {
				return adobe::binder_ptr_t( new gphoto_state_pump_t );
			}
		};
		adobe::value_t make_gphoto_state() {
			return adobe::value_t( adobe::binder_ptr_t( new gphoto_state_pump_t ) );
		}
		class gphoto_camera_list_pump_t : public adobe::binder_t {
			gphoto_import_t::scoped_connection_t	connection_m;
			gphoto_import_t::gphoto_import_ptr_t	ref_m;
			adobe::sheet_t::monitor_value_t		value_monitor_m;
			void update( const adobe::array_t& camera_list ) {
				if( value_monitor_m ) value_monitor_m( adobe::value_t( camera_list ) );
			}
		  public:
			gphoto_camera_list_pump_t() {}
			virtual ~gphoto_camera_list_pump_t() {}
			void set( const adobe::value_t& ) {}
			void set_signals( const adobe::sheet_t::monitor_value_t& value_monitor, const adobe::sheet_t::monitor_active_t& active_monitor ) {
				value_monitor_m = value_monitor;
				if( active_monitor ) active_monitor( false );
				if( !value_monitor ) return;
				if( !ref_m ) {
					ref_m = gphoto_import_t::getInstance();
					connection_m = ref_m->subscribe_camera_list( boost::bind( &gphoto_camera_list_pump_t::update, this, _1 ) );
				} else value_monitor_m( adobe::value_t( ref_m->get_camera_list() ) );
			}
			void substitute( const adobe::name_t&, const adobe::value_t& ) {}
			void invoke() {}
			adobe::binder_ptr_t copy() const {
				return adobe::binder_ptr_t( new gphoto_camera_list_pump_t );
			}
		};
		adobe::value_t make_gphoto_camera_list() {
			return adobe::value_t( adobe::binder_ptr_t( new gphoto_camera_list_pump_t ) );
		}
		class gphoto_selected_camera_pump_t : public adobe::binder_t {
			gphoto_import_t::scoped_connection_t	camera_connection_m;
			gphoto_import_t::scoped_connection_t	list_connection_m;
			gphoto_import_t::gphoto_import_ptr_t	ref_m;
			adobe::sheet_t::monitor_value_t		value_monitor_m;
			adobe::sheet_t::monitor_active_t	active_monitor_m;
			//
			// We report the camera with our value, but we also watch
			// the list. If it is empty then we become inactive.
			//
			void update_camera( const gphoto_import_t::camera_t& camera ) {
				if( value_monitor_m ) value_monitor_m( adobe::value_t( camera ) );
			}
			void update_list( const adobe::array_t& list ) {
				if( !active_monitor_m ) return;
				active_monitor_m( !list.empty() );
			}
		  public:
			gphoto_selected_camera_pump_t() {}
			virtual ~gphoto_selected_camera_pump_t() {}
			void set( const adobe::value_t& val ) {
				gphoto_import_t::camera_t camera;
				if( val.get( camera ) ) ref_m->set_camera( camera );
			}
			void set_signals( const adobe::sheet_t::monitor_value_t& value_monitor, const adobe::sheet_t::monitor_active_t& active_monitor ) {
				value_monitor_m = value_monitor;
				active_monitor_m = active_monitor;
				if( !ref_m ) {
					ref_m = gphoto_import_t::getInstance();
					camera_connection_m = ref_m->subscribe_selected_camera( boost::bind( &gphoto_selected_camera_pump_t::update_camera, this, _1 ) );
					list_connection_m = ref_m->subscribe_camera_list( boost::bind( &gphoto_selected_camera_pump_t::update_list, this, _1 ) );
					return;
				}
				update_camera( ref_m->get_camera() );
				update_list( ref_m->get_camera_list() );
			}
			void substitute( const adobe::name_t&, const adobe::value_t& ) {}
			void invoke() {}
			adobe::binder_ptr_t copy() const {
				return adobe::binder_ptr_t( new gphoto_selected_camera_pump_t );
			}
		};
		adobe::value_t make_gphoto_selected_camera() {
			return adobe::value_t( adobe::binder_ptr_t( new gphoto_selected_camera_pump_t ) );
		}
		class gphoto_import_pump_t : public adobe::binder_t {
			adobe::binder_helper_t			album_binder_m;
			std::string				album_m;
			adobe::dictionary_t			dictionary_m;
			adobe::sheet_t&				sheet_m;
			bfs::path				target_m;
			void update_album( const adobe::value_t& album ) { album.get( album_m ); }
		  public:
			gphoto_import_pump_t( const adobe::dictionary_t& params, adobe::sheet_t& sheet, const bfs::path& target ) : album_binder_m( sheet ), dictionary_m( params ), sheet_m( sheet ), target_m( target ) {
				//
				// Extract the album value from the dictionary.
				//
				adobe::value_t album = params.at( adobe::static_name_t( "album" ) );
				album_binder_m.initialize( album, boost::bind( &gphoto_import_pump_t::update_album, this, _1 ), 0 );
			}
			virtual ~gphoto_import_pump_t() {}
			void set( const adobe::value_t& ) {}
			void set_signals( const adobe::sheet_t::monitor_value_t&, const adobe::sheet_t::monitor_active_t& ) {}
			void substitute( const adobe::name_t& name, const adobe::value_t& val ) {
				album_binder_m.substitute( name, val );
			}
			void invoke() {
				gphoto_import_t::gphoto_import_ptr_t ref( gphoto_import_t::getInstance() );
				if( !ref ) return;
				ref->import( album_m, target_m );
			}
			adobe::binder_ptr_t copy() const {
				return adobe::binder_ptr_t( new gphoto_import_pump_t( dictionary_m, sheet_m, target_m ) );
			}
		};
		adobe::value_t make_gphoto_import( const adobe::dictionary_t& params, adobe::sheet_t& sheet, const bfs::path& target ) {
			return adobe::value_t( adobe::binder_ptr_t( new gphoto_import_pump_t( params, sheet, target ) ) );
		}
		class gphoto_rescan_action_t : public adobe::binder_t {
		  public:
			gphoto_rescan_action_t() {}
			virtual ~gphoto_rescan_action_t() {}
			void set( const adobe::value_t& ) {}
			void set_signals( const adobe::sheet_t::monitor_value_t&, const adobe::sheet_t::monitor_active_t& ) {}
			void substitute( const adobe::name_t&, const adobe::value_t& ) {}
			void invoke() {
				gphoto_import_t::gphoto_import_ptr_t ref( gphoto_import_t::getInstance() );
				ref->rescan_cameras();
			}
			adobe::binder_ptr_t copy() const {
				return adobe::binder_ptr_t( new gphoto_rescan_action_t );
			}
		};
		adobe::value_t make_gphoto_rescan() {
			return adobe::value_t( adobe::binder_ptr_t( new gphoto_rescan_action_t ) );
		}
	}
}

